package net.pragyah.scalby

import scala.collection.immutable.Map
import net.pragyah.scalgorithms.graphs._;

object ProbabilityDistribution{
  def apply[A]() = new ProbabilityDistribution[A]()
}

class ProbabilityDistribution[A] {

  var distMap = Map[Variable[A], //map if variable ... against
                     Map[A, // a map of the value ... against
                         Map[Map[Variable[A],A], // a map of parent variable and values ... against 
                             float] // the conditional probability
                         ]
                     ]()
  
  def apply(X:Variable[A],x:A,aA:Map[Variable[A],A],Px:float){
   
   var mapAMap:Map[A,Map[Map[Variable[A],A],float]] = null
   
   if(distMap.contains(X)){
     mapAMap=distMap(X)
   }else{
     mapAMap = Map[A,Map[Map[Variable[A],A],float]]();
   }
   
   var mapMapFlt:Map[Map[Variable[A],A],float] = null
   if(mapAMap.contains(x)){
     mapMapFlt = mapAMap(x);
   }else{
    mapMapFlt = Map[Map[Variable[A],A],float](); 
   }
   
   mapMapFlt = mapMapFlt + (aA -> Px); 
   mapAMap = mapAMap + (x -> mapMapFlt)
   distMap = distMap + (X -> mapAMap)
  }

  def apply(X:Variable[A],x:A,aA:Map[Variable[A],A]) : Option[float] = {
    
   val mapAMap = distMap(X);
   if(mapAMap == null) return None
   var mapMapFlt = mapAMap(x);
   if(mapMapFlt == null) return None
   
   
   if(mapMapFlt.contains(aA)) 
     return Some(mapMapFlt(aA))
   else{//by now ...if you have not reach the desired place .. it's possible that the map aA contains a little more than what 
     // X is dependent upon ... let's say ... X is dependent upon Y and Z ... but instead of having Y-y and Z-z in the map ..
    // aA also has some extra pairs P-p Q-q .... all you need to do is ignore P and Q and find out what P(X = x)is when Y is y and Z is z 

	    val aASet = Set[Tuple2[Variable[A],A]]()++ aA
	    
	    val map = getDependencies(X,x)
	    map.keySet.foreach(keys => {
	      //get each key .. i.e. mappings of predecessors and their given values ... as a set
	                         val set = Set[Tuple2[Variable[A],A]]()  ++ keys
	                         //see if this set is contained within the set given as an argument 
	                         if(set.subsetOf(aASet))
	                           return Some(map(keys))
	                       })
    }
   None
   
  }
  
  /*
   * This function returns the conditional probability given a map of Variable and their
   * corresponding values out of which some (or all) could be X's parents.
   * What this method does is to cull out relevant Variable-Value combinations from the given
   * map and looks up the probability in distMap
   * e.g.  X1 is dependent on X2 and X3 hence the conditional probability will include only the later two
   * This method accepts a map of X2,X3,X4 and X5 (and more .. . for all it cares) and their corresponding values and pulls out X2 and X3
   * along with their corresponding values
   * If there are no X2 and X3 .. it returns back a None
   * 
   */
  def getConditionalForAll(X:Variable[A],x:A,aA:Map[Variable[A],A]) : Option[float] = {

    None//get the given maps out as a set .... 
  }
  
  //get the map of all other non-descendents (and their values) of a variable (and given it's value) and their values
  // given one of the non=descendent and its value
  def getOtherDependencies(X:Variable[A],x:A,Y:Variable[A],y:A) : Map[Map[Variable[A],A],float]= {
    
    var returnMap = Map[Map[Variable[A],A],float]()

    
   val mapAMap = distMap(X);
   if(mapAMap == null) return null
   var mapMapFlt = mapAMap(x);
   if(mapMapFlt == null) return null
    
   mapMapFlt.keySet.filter(_(Y) == y).foreach(aA => {
                      returnMap = returnMap + ( (aA - Y) -> mapMapFlt(aA))
                                              }
   )
   returnMap
  }

  //get all the dependency maps .... for the given variable X and given value
  def getDependencies(X:Variable[A],x:A) : Map[Map[Variable[A],A],float]= {
  
    
    val mapAMap = distMap(X);
   if(mapAMap == null) return null
   return mapAMap(x);

  }
  
}
